import {
    arrayMethods
} from './array.js'
import Dep from './dep.js'
// 判断是不是对象 小工具方法
function isObject(data) {
    return typeof data === 'object' && data !== null;
}

function def(data, key, value) {
    Object.defineProperty(data, key, {
        enumerable: false,
        configurable: false,
        value
    })
}

// 进行getter setter 劫持赋值操作
function defineReactie(data, key, val) {
    const dep = new Dep()
    // 在劫持的时候可能会遇到 {person:{name:'w'}} 这种格式
    // 因此 需要对{name:'w'} 继续做劫持
    observe(val); // 递归实现深度检测
    Object.defineProperty(data, key, {
        configurable: true,
        enumerable: true,
        get() {
            if (Dep.target) { // 如果取值时有watcher
                console.log('get')

                dep.depend(); // 让watcher保存dep，并且让dep 保存watcher
            }
            return val
        },
        set(newVal) {
            if (newVal === val) return
            console.log('set')
            // 假如当前值是一个基本类型 例如 a = 1，此时用户将a赋值为a={name:'w'}
            // 这个时候是需要继续对 {name:'w'} 做监听的
            observe(newVal)
            val = newVal
            dep.notify(); // 通知渲染watcher去更新
        }
    })
}
// 这里主要做的是对 数据劫持
class Observer {
    constructor(value) {
        // 将每个value 和 当期数据劫持的对象进行一个绑定
        // 相当于给每一个监控过的对象都增加一个__ob__属性
        def(value, '__ob__', this);
        if (Array.isArray(value)) {
            // 对数组进行劫持 因为如果是数组的话会对索引进行观测 因为会导致性能问题
            // 所以解决这个问题 我需要对数组类型做特殊处理，但是例如push shift unshift 
            // 通过这些数组方法 加入到数组的值依然需要做getter 和setter 绑定的因此需要对这些方法做扩展
            value.__proto__ = arrayMethods;
            // 如果数组里放的是对象我再监控
            this.observerArray(value);

        } else {
            this.walk(value)
        }
    }
    observerArray(value) { // [{}]
        for (let i = 0; i < value.length; i++) {
            observe(value[i]);
        }
    }
    walk(data) {
        Object.keys(data).forEach(key => defineReactie(data, key, data[key]))
    }
}

export function observe(data) {
    // 首先Vue data格式 只有是函数和对象两种，即使是函数最后返回的也是对象
    // 所以在第一次的时候需要判断当前是否是对象，格式是否是正确格式
    if (!isObject(data)) return
    return new Observer(data)
}